%{

#include <stdio.h>
#include <string.h>
#include <alloc.h>
#include <types.h>
#include "arrays.h"

int yylex( void );
int yyparse( void );
char * getenv( char * );
void exit( unsigned int );
void yyerror( LPSTR );

typedef enum { CONSTANT, VARREF, OPTIONAL } Type;
typedef enum { VT_NORMAL, VT_WILD, VT_LIST, VT_TOKENS,
               VT_BLOCK, VT_STRING, VT_LOGIC } VARTYPE;

WORD    wLevel   = 3;
VARTYPE wVarType = 0;
WORD    wBlanks  = 0;

typedef struct
{
   Type   wType;
   LPVOID pData;
   WORD   wFlags;
   WORD   wBlanks;
} CMDITEM, * PCMDITEM;

PCMDITEM Constant( LPSTR szConst );
void DrawItem( PCMDITEM pCmdItem );

typedef struct
{
   LPSTR szToken;
   WORD  wBlanks;
} LINEITEM, * PLINEITEM;

PLINEITEM LineItem( LPSTR szToken );

typedef struct
{
   LPSTR  szName;
   WORD   wType;
   BOOL   bMatched;
   PARRAY aValues;
} VAR, * PVAR;

void AddRightVar( LPSTR szVar );

typedef struct
{
   BOOL   bRepeat;         // Repeatable subcommands
   WORD   wRepeats;
   PARRAY aItems, aVars, aResult;
} COMMAND, * PCOMMAND;

LPSTR str( char c )
{
   LPSTR szChr = ( LPSTR ) malloc( 2 );

   szChr[ 0 ] = c;
   szChr[ 1 ] = 0;

   return szChr;
}

PITEM StartWith( PITEM pItem, LPSTR szText )
{
   while( pItem )
   {
      if( ! strcmp( ( LPSTR ) pItem->pData, szText ) )
         return pItem;
      else
         pItem = pItem->pNext;
   }
   return 0;
}

void DrawItem( PCMDITEM pCmdItem )
{
   switch( pCmdItem->wType )
   {
      case CONSTANT:
           printf( "%*s constant: %s %i\n", wLevel, "",
                   ( LPSTR ) pCmdItem->pData, pCmdItem->wBlanks );
           break;

      case VARREF:
           printf( "%*s   varref: %s %i", wLevel, "",
                   ( ( PVAR ) pCmdItem->pData )->szName, pCmdItem->wBlanks );
           switch( pCmdItem->wFlags )
           {
              case VT_LIST:
                   printf( " list" );
                   break;
           }
           printf( "\n" );
           break;

      case OPTIONAL:
           printf( "%*s optional:\n", wLevel, "" );
           wLevel += 3;
           AEval( ( ( PCOMMAND ) pCmdItem->pData )->aItems, ( PFUNCPVOID ) DrawItem );
           wLevel -= 3;
           break;
   }
}

void DrawToken( PLINEITEM pLineItem )
{
   printf( "%*s", pLineItem->wBlanks, "" );
   printf( "%s", pLineItem->szToken );
}

void DrawValue( PCMDITEM pCmdItem )
{
   PVAR pVar;
   WORD w;
   LPSTR szValue;
   PLINEITEM pLineItem;

   switch( pCmdItem->wType )
   {
      case CONSTANT:
           printf( "%*s", pCmdItem->wBlanks, "" );
           printf( "%s", ( LPSTR ) pCmdItem->pData );
           break;

      case VARREF:
           pVar = pCmdItem->pData;
           printf( "%*s", pCmdItem->wBlanks, "" );

           switch( pCmdItem->wFlags )
           {
              case VT_NORMAL:
                   for( w = 0; w < Len( pVar->aValues ); w++ )
                   {
                      pLineItem = AGet( pVar->aValues, w );
                      if( w > 0 )
                         printf( "%*s", pLineItem->wBlanks, "" );
                      printf( pLineItem->szToken );
                   }
                   break;

              case VT_BLOCK:
                   printf( "{||" );
                   for( w = 0; w < Len( pVar->aValues ); w++ )
                   {
                      pLineItem = AGet( pVar->aValues, w );
                      printf( "%*s", pLineItem->wBlanks, "" );
                      printf( pLineItem->szToken );
                   }
                   printf( " }" );
                   break;

              case VT_STRING:
                   if( pVar->wType == VT_LIST )
                   {
                      for( w = 0; w < Len( pVar->aValues ); w++ )
                      {
                         printf( "\"%s\"", ( ( PLINEITEM ) AGet( pVar->aValues, w ) )->szToken );

                         if( w < Len( pVar->aValues ) - 1 )
                            printf( ", " );
                      }
                   }
                   else
                   {
                      printf( "\"" );
                      pLineItem = AGet( pVar->aValues, 0 );
                      printf( pLineItem->szToken );
                      printf( "\"" );
                   }
                   break;
           }
           break;

      case OPTIONAL:
           break;
   }
}

void DrawCommand( PCOMMAND pCmd )
{
   printf( "Command:\n" );
   AEval( pCmd->aItems, ( PFUNCPVOID ) DrawItem );
   printf( "    =>\n" );
   AEval( pCmd->aResult, ( PFUNCPVOID ) DrawItem );
}

void DrawTranslate( PCOMMAND pCmd )
{
   printf( "Translate:\n" );
   AEval( pCmd->aItems, ( PFUNCPVOID ) DrawItem );
   printf( "    =>\n" );
   AEval( pCmd->aResult, ( PFUNCPVOID ) DrawItem );
}

#define YYDEBUG 0

%}

%union
{
   char * string;
   int number;
   void * lpvoid;
};

%token <string> XCOMMAND XTRANSLATE TOKEN ARROW DOTS STRING
%token INCLUDE DEFINE IFDEF IFNDEF ELSE ENDIF

%right '\n'

%type <string>  const LeftVar RightVar
%type <lpvoid>  items LeftOptional
%type <number>  special

%%

source  : crlf
        | include
        | define
        | ifdef
        | else
        | endif
        | ifndef
        | line                  { CheckCommand(); }
        | command
        | translate
        | source include
        | source define
        | source ifdef
        | source ifndef
        | source else
        | source endif
        | source line           { CheckCommand(); }
        | source command
        | source translate
        | source crlf
        ;

crlf    : '\n' crlf             { wCRLFs++; }
        | '\n'                  { wCRLFs = 1; }
        ;

include : INCLUDE TOKEN
        ;

define  : DEFINE TOKEN
        | DEFINE TOKEN TOKEN
        ;

ifdef   : IFDEF TOKEN
        ;

else    : ELSE
        ;

endif   : ENDIF
        ;

ifndef  : IFNDEF TOKEN
        ;

line    : TOKEN                 { ASize( aLine, 0 ); AAdd( aLine, LineItem( $1 ) ); }
        | STRING                { ASize( aLine, 0 ); AAdd( aLine, LineItem( $1 ) ); }
        | line TOKEN            { wBlanks ? wBlanks = 1: 0; AAdd( aLine, LineItem( $2 ) ); }
        | line STRING           { wBlanks ? wBlanks = 1: 0; AAdd( aLine, LineItem( $2 ) ); }
        | line special          { wBlanks ? wBlanks = 1: 0; AAdd( aLine, LineItem( str( $2 ) ) ); }
        ;

command : XCOMMAND items ARROW
        | XCOMMAND items ARROW result
        ;

translate : XTRANSLATE items ARROW result { AAdd( aTranslates, ATail( aCommands ) );
                                            ADelLast( aCommands ); }
        ;

items   : const                     { AAdd( aCommands, Command() ); AAdd( LastCommand()->aItems, Constant( $1 ) ); }
        | items const               { AAdd( LastCommand()->aItems, Constant( $2 ) ); }
        | items special             { AAdd( LastCommand()->aItems, Constant( str( $2 ) ) ); }
        | items LeftVar             { AddLeftVarRef( $2 ); }
        | items LeftOptional
        ;

LeftOptItems : const                { AAdd( LastCommand()->aItems, Constant( $1 ) ); }
        | LeftVar                   { AddLeftVarRef( $1 ); }
        | special                   { AAdd( LastCommand()->aItems, Constant( str( $1 ) ) ); }
        | LeftOptional
        | LeftOptItems const        { AAdd( LastCommand()->aItems, Constant( $2 ) ); }
        | LeftOptItems LeftVar      { AddLeftVarRef( $2 ); }
        | LeftOptItems special      { AAdd( LastCommand()->aItems, Constant( str( $2 ) ) ); }
        | LeftOptItems LeftOptional
        ;

RightOptItems : const                 { AAdd( LastCommand()->aItems, Constant( $1 ) ); }
        | RightVar                    { AddRightVar( $1 ); }
        | special                     { AAdd( LastCommand()->aItems, Constant( str( $1 ) ) ); }
        | RightOptItems const         { AAdd( LastCommand()->aItems, Constant( $2 ) ); }
        | RightOptItems RightVar      { AddRightVar( $2 ); }
        | RightOptItems special       { AAdd( LastCommand()->aItems, Constant( str( $2 ) ) ); }
        | RightOptItems RightOptional
        ;

const   : TOKEN                              { $$ = $1; }
        ;

LeftVar : lt TOKEN gt                      { wVarType = VT_NORMAL; $$ = $2; }
        | lt '*' TOKEN '*' gt              { wVarType = VT_WILD;   $$ = $3; }
        | lt TOKEN ',' DOTS gt             { wVarType = VT_LIST;   $$ = $2; }
        | lt TOKEN ':' tokenList gt        { wVarType = VT_TOKENS; $$ = $2; }
        ;

RightVar : lt TOKEN gt                     { wVarType = VT_NORMAL; $$ = $2; }
        | lt '{' TOKEN '}' gt              { wVarType = VT_BLOCK;  $$ = $3; }
        | lt '(' TOKEN ')' gt              { wVarType = VT_STRING; $$ = $3; }
        | lt '.' TOKEN '.' gt              { wVarType = VT_LOGIC;  $$ = $3; }
        ;

LeftOptional : '['             { AAdd( aOptionals, SubCommand( LastCommand() ) ); }
                LeftOptItems   { aOptional = ATail( aOptionals );
                                 ADelLast( aOptionals );
                                 AAdd( LastCommand()->aItems, Optional( aOptional ) ); }
                ']'
        ;

RightOptional : '['            { AAdd( aOptionals, SubCommand( LastCommand() ) ); }
                 RightOptItems { aOptional = ATail( aOptionals );
                                 ADelLast( aOptionals );
                                 AAdd( LastCommand()->aResult, Optional( aOptional ) ); }
                 ']'
        ;

tokenList : tokens
        | tokenList ',' tokens
        ;

tokens  : TOKEN
        | tokens TOKEN
        ;

result  : TOKEN                  { AAdd( LastCommand()->aResult, Constant( $1 ) ); }
        | RightOptional
        | RightVar               { AddRightVar( $1 ); }
        | special                { AAdd( LastCommand()->aResult, Constant( str( $1 ) ) ); }
        | result TOKEN           { AAdd( LastCommand()->aResult, Constant( $2 ) ); }
        | result RightVar        { AddRightVar( $2 ); }
        | result RightOptional
        | result special         { AAdd( LastCommand()->aResult, Constant( str( $2 ) ) ); }
        ;

special : '{'                    { $$ = '{'; }
        | '}'                    { $$ = '}'; }
        | '('                    { $$ = '('; }
        | ')'                    { $$ = ')'; }
        | ','                    { $$ = ','; }
        | lt                     { $$ = '<'; }
        | gt                     { $$ = '>'; }
        ;

lt      : '<'
        ;

gt      : '>'
        ;

%%

WORD   lineno = 1, tokenpos = 0, wCmdCheck = 0;
BOOL   lDebug = FALSE;
PARRAY aCommands, aOptionals, aTranslates, aLine;
LPVOID aOptional;
char   linebuf[ 1024 ];
char   chr[] = " ";
WORD   wCRLFs = 0;

PCOMMAND LastCommand( void )
{
   if( Len( aOptionals ) )
      return ( PCOMMAND ) ATail( aOptionals );
   else
      return ( PCOMMAND ) ATail( aCommands );
}

//--------------- constructors -----------------------------------------------

PCMDITEM CmdItem( void )
{
   PCMDITEM pNew = ( PCMDITEM ) malloc( sizeof( CMDITEM ) );

   pNew->wType = 0;
   pNew->pData = 0;

   return pNew;
}

PLINEITEM LineItem( LPSTR szToken )
{
   PLINEITEM pLineItem = ( PLINEITEM ) malloc( sizeof( LINEITEM ) );

   pLineItem->szToken = szToken;
   pLineItem->wBlanks = wBlanks;
   wBlanks = 0;

   return pLineItem;
}

PCMDITEM Constant( LPSTR szConst )
{
   PCMDITEM pNew = CmdItem();

   pNew->wType   = CONSTANT;
   pNew->pData   = szConst;
   pNew->wBlanks = wBlanks ? 1: 0;
   wBlanks = 0;

   return pNew;
}

PCMDITEM VarRef( PVAR pVar )
{
   PCMDITEM pNew = CmdItem();

   pNew->wType   = VARREF;
   pNew->pData   = pVar;
   pNew->wFlags  = wVarType;
   pNew->wBlanks = wBlanks ? 1: 0;
   wBlanks = 0;

   return pNew;
}

PVAR Var( LPSTR szName )
{
   PVAR pVar = ( PVAR ) malloc( sizeof( VAR ) );

   pVar->szName  = szName;
   pVar->aValues = Array();
   pVar->wType   = VT_NORMAL;

   return pVar;
}

PCOMMAND Command( void )
{
   PCOMMAND pCmd = ( PCOMMAND ) malloc( sizeof( COMMAND ) );

   pCmd->bRepeat  = FALSE;
   pCmd->wRepeats = 0;
   pCmd->aItems   = Array();
   pCmd->aVars    = Array();
   pCmd->aResult  = Array();

   return pCmd;
}

PCOMMAND SubCommand( PCOMMAND pParentCmd )
{
   PCOMMAND pCmd = ( PCOMMAND ) malloc( sizeof( COMMAND ) );

   pCmd->bRepeat  = FALSE;
   pCmd->wRepeats = 0;
   pCmd->aItems   = Array();
   pCmd->aVars    = pParentCmd->aVars;
   pCmd->aResult  = pCmd->aItems;

   return pCmd;
}

PCMDITEM Optional( PCOMMAND aCommand )
{
   PCMDITEM pNew = CmdItem();

   pNew->wType = OPTIONAL;
   pNew->pData = aCommand;

   return pNew;
}

//------------------------------------------------------------------------------

BOOL FindVar( LPVOID pVar, LPSTR szName )
{
   return ( BOOL ) ! strcmp( ( ( PVAR ) pVar )->szName, szName );
}

void AddLeftVarRef( LPSTR szVar )
{
   PCOMMAND pLast = LastCommand();
   WORD wAt = AScanC( pLast->aVars, FindVar, szVar );
   PVAR pVar;

   if( ! wAt )
   {
      AAdd( pLast->aVars, pVar = Var( szVar ) );
      pVar->wType = wVarType;
      AAdd( pLast->aItems, VarRef( pVar ) );
   }
   else
      yyerror( "variable already defined" );
}

void AddRightVar( LPSTR szVar )
{
   PCOMMAND pLast = LastCommand();
   WORD wAt = AScanC( pLast->aVars, FindVar, szVar );
   PVAR pVar;

   if( ! wAt )
      yyerror( "variable not found" );
   else
      AAdd( pLast->aResult, VarRef( ( PVAR ) AGet( pLast->aVars, wAt - 1 ) ) );
}

PITEM NewOptional( PITEM pItem )
{
   AAdd( LastCommand()->aItems, pItem );
   return pItem;
}

WORD Min( WORD w1, WORD w2 )
{
   return ( w1 < w2 ) ? w1: w2;
}

BOOL IsCommand( PCOMMAND pCmd, WORD * pwLineItem, PCMDITEM pNextCmdItem )
{
   WORD wCmdItem = 0, wLineItem = pwLineItem ? * pwLineItem: 0;
   BOOL bOk = TRUE;
   PCMDITEM pCmdItem, pSubItem;
   PLINEITEM pLineItem;
   PVAR pVar;
   WORD wGrouping = 0;
   BOOL bGrouping = FALSE;

   while( ( wLineItem < Len( aLine ) ) && bOk )
   {
      pLineItem = ( PLINEITEM ) AGet( aLine, wLineItem );

      if( wCmdItem == Len( pCmd->aItems ) )
         return FALSE;

      pCmdItem = ( PCMDITEM ) AGet( pCmd->aItems, wCmdItem );

      switch( pCmdItem->wType )
      {
         case CONSTANT:
              if( ( BOOL ) strcmp( pLineItem->szToken,
                                   ( LPSTR ) pCmdItem->pData ) )
                 return FALSE;
              break;

         case VARREF:
              pVar = ( PVAR ) pCmdItem->pData;

              if( pVar->wType != VT_LIST )
              {
                 AAdd( pVar->aValues, pLineItem );
                 bGrouping = TRUE;

                 while( ( wLineItem + 1 < Len( aLine ) ) && bGrouping )
                 {
                    pLineItem = ( PLINEITEM ) AGet( aLine, ++wLineItem );

                    if( ( ! pNextCmdItem ) && ( wCmdItem == Len( pCmd->aItems ) ) )
                        AAdd( pVar->aValues, pLineItem );
                    else
                    {
                       if( ! wGrouping )
                       {
                          if( ( wCmdItem + 1 ) < Len( pCmd->aItems ) )
                             pCmdItem = ( PCMDITEM ) AGet( pCmd->aItems, wCmdItem + 1 );
                          else
                             pCmdItem = pNextCmdItem;

                          if( pCmdItem )
                          {
                             switch( pCmdItem->wType )
                             {
                                case CONSTANT:
                                     if( ! strcmp( pLineItem->szToken, ( LPSTR ) pCmdItem->pData ) )
                                     {
                                        bGrouping = FALSE;
                                        wLineItem--;
                                        bOk = ( pCmdItem != pNextCmdItem );
                                     }
                                     else
                                        AAdd( pVar->aValues, pLineItem );
                                     break;

                                case VARREF:
                                     AAdd( pVar->aValues, pLineItem );
                                     break;

                                case OPTIONAL:
                                     pSubItem = ( PCMDITEM ) AGet( ( ( PCOMMAND ) pCmdItem->pData )->aItems, 0 );
                                     if( pSubItem && ( pSubItem->wType == CONSTANT ) )
                                     {
                                        if( ! strcmp( pLineItem->szToken, ( LPSTR ) pSubItem->pData ) )
                                        {
                                           bGrouping = FALSE;
                                           wLineItem--;
                                           bOk = ( pCmdItem != pNextCmdItem );
                                        }
                                        else
                                           AAdd( pVar->aValues, pLineItem );
                                     }
                                     else
                                        AAdd( pVar->aValues, pLineItem );
                                     break;
                             }
                          }
                          else
                             AAdd( pVar->aValues, pLineItem );
                       }
                       else
                          AAdd( pVar->aValues, pLineItem );

                       if( pLineItem->szToken[ 0 ] == '(' ||
                           pLineItem->szToken[ 0 ] == '[' ||
                           pLineItem->szToken[ 0 ] == '{' )
                          wGrouping++;

                       if( pLineItem->szToken[ 0 ] == ')' ||
                           pLineItem->szToken[ 0 ] == ']' ||
                           pLineItem->szToken[ 0 ] == '}' )
                          --wGrouping;
                    }
                 }
              }
              else
              {
                 AAdd( pVar->aValues, pLineItem );

                 while( wLineItem + 1 < Len( aLine ) &&
                        ! strcmp( ( ( PLINEITEM ) AGet( aLine, wLineItem + 1 ) )->szToken, "," ) )
                 {
                    wLineItem += 2;
                    if( wLineItem < Len( aLine ) )
                       AAdd( pVar->aValues,
                             ( PLINEITEM ) AGet( aLine, wLineItem ) );
                 }
              }
              break;

         case OPTIONAL:
              IsCommand( ( PCOMMAND ) pCmdItem->pData, &wLineItem,
                         ( wCmdItem + 1 < Len( pCmd->aItems ) ) ?
                         ( PCMDITEM ) AGet( pCmd->aItems, wCmdItem + 1 ):
                         pNextCmdItem );
              break;
      }
      wLineItem++;
      wCmdItem++;
   }

   if( wCmdItem < Len( pCmd->aItems ) )
      return FALSE;

   if( pwLineItem )
      * pwLineItem = wLineItem - 1;

   return bOk;
}

void Translate( WORD wAt )
{
   PCOMMAND pCmd = ( PCOMMAND ) AGet( aCommands, wAt - 1 );

   AEval( pCmd->aResult, ( PFUNCPVOID ) DrawValue );
}

void CheckCommand( void )
{
   WORD wAt, w;

   if( Len( aCommands ) )
      wAt = AScanPWPV( aCommands, ( PSCANPWPV ) IsCommand, 0, 0 );

   if( wAt )
   {
      // set the ammount of blanks of the first token in the line to
      // the first item of the result
      ( ( PCMDITEM ) AGet( ( ( PCOMMAND )
      AGet( aCommands, wAt - 1 ) )->aResult, 0 ) )->wBlanks =
      ( ( PLINEITEM ) AGet( aLine, 0 ) )->wBlanks;

      Translate( wAt );
   }
   else
   {
      AEval( aLine, ( PFUNCPVOID ) DrawToken );
   }
   for( w = 0; w < wCRLFs; w++ )
      printf( "\n" );
}

void yyerror( LPSTR szMsg )
{
   printf( "%s at line %d: \n%s \n", szMsg, lineno, linebuf );
   printf( "%*s\n", tokenpos, "^" );
   exit( 1 );
}

int main( int argc, char * argv[] )
{
   extern FILE * yyin, * yyout;

   printf( "Fivep 1.0 - The Clipper compatible PreProcessor\n" );
   printf( "(c) FiveTech 1993-7\n" );

   if( argc > 1 )
   {
      if( ! ( yyin = fopen( argv[ 1 ], "r" ) ) )
      {
         printf( "   error: cannot open %s", argv[ 1 ] );
         return 1;
      }
      printf( "preprocessing %s\n", argv[ 1 ] );

      // yydebug = BOOL( argc > 2 ) && strcmp( argv[ 2 ], "/d" );
      lDebug = ( BOOL ) ( argc > 2 ) && ! strcmp( argv[ 2 ], "\/d" );

      aCommands   = Array();
      aOptionals  = Array();
      aTranslates = Array();
      aLine       = Array();

      yyparse();

      printf( "done!\n" );
      printf( "Total commands   defined: %i\n", aCommands->wItems );
      printf( "Total translates defined: %i\n", aTranslates->wItems );

      AEval( aCommands,   ( PFUNCPVOID ) DrawCommand );
      // AEval( aTranslates, ( PFUNCPVOID ) DrawTranslate );

      return 0;
   }
   else
   {
      printf( "   syntax: fivep <source> [<target>] [options]\n\n" );
      printf( "   options:\n" );
      printf( "       /d   debug info\n" );
   }

   return 0;
}

